﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;
using StreetPacMan.FrontEnd.Infra;
using StreetPacMan.Server;
using StructureMap;

namespace StreetPacMan.FrontEnd
{
    public static class AutoWire
    {
        public static void Execute(Expression<Action<IServerFacade>> action, HttpContext context)
        {
            var info = (MethodInfo)ReflectionAssist.Get(action);
            ExecuteInternal(context, info);
        }
        public static void Execute(Expression<Func<IServerFacade, object>> func, HttpContext context)
        {
            var info = (MethodInfo)ReflectionAssist.Get(func);
            ExecuteInternal(context, info);
        }

        private static void ExecuteInternal(HttpContext context, MethodInfo info)
        {
            var serverFacade = ObjectFactory.GetInstance<IServerFacade>();

            var nameValueCollection = context.Request.QueryString;

            object firstArg;
            try
            {
                firstArg = info.GetParameters()
                    .Select(x => GetFor(x.ParameterType, nameValueCollection))
                    .FirstOrDefault();
            }
            catch (ArgumentException e)
            {
                context.Response.ContentType = "text/html";
                context.Response.Write(JsonPSerializer.GetResponse(context, new RequestError(e)));
                return;
            }

            object[] parameters = firstArg == null ? null : new [] { firstArg };
            object retVal;
            try
            {
                retVal = info.Invoke(serverFacade, parameters);
            }
            catch (TargetInvocationException e)
            {
                context.Response.ContentType = "text/html";
                context.Response.Write(JsonPSerializer.GetResponse(context, new RequestError(e.InnerException)));
                return;
            }

            if (retVal != null)
            {
                context.Response.ContentType = "text/html";
                context.Response.Write(JsonPSerializer.GetResponse(context, retVal));
            }
        }

        private static object GetFor(Type objectType, NameValueCollection nvc)
        {
            var ctorWithMostArguemnts = objectType
                .GetConstructors(BindingFlags.Public|BindingFlags.Instance)
                .OrderByDescending(x => x.GetParameters().Length)
                .First();

            var args = ctorWithMostArguemnts
                .GetParameters()
                .Select(x => GetArg(nvc, x.ParameterType, x.Name.AsPascalCase()))
                .ToArray();

            return ctorWithMostArguemnts.Invoke(args);
        }

        private static object GetArg(NameValueCollection source, Type parameterType, string argName)
        {
            var stringValue = source[argName];
            if (stringValue == null)
            {
                throw new ArgumentException(string.Format("Missing {0} arguemnt of type {1}", argName, parameterType.Name));
            }
            if (parameterType == typeof(int))
            {
                int result;
                if (int.TryParse(stringValue, out result) == false)
                {
                    throw new ArgumentException(string.Format("The value of {0} is not a legal int ({1})", argName, stringValue));
                }
                return result;
            }
            if (parameterType.IsEnum)
            {
                int result;
                if (int.TryParse(stringValue, out result) == false)
                {
                    throw new ArgumentException(string.Format("The value of {0} is not a legal int ({1})", argName, stringValue));
                }
                if (Enum.IsDefined(parameterType,result) == false)
                {
                    throw new ArgumentException(string.Format("The value '{0}' is not a legal {1} value", result, parameterType.Name));
                }
                return Enum.ToObject(parameterType, result);
            }
            if (parameterType == typeof(double))
            {
                double result;
                if (double.TryParse(stringValue, out result) == false)
                {
                    throw new ArgumentException(string.Format("The value of {0} is not a legal double ({1})", argName, stringValue));
                }
                return result;
            }
            if (parameterType == typeof(bool))
            {
                bool result;
                if (bool.TryParse(stringValue, out result) == false)
                {
                    throw new ArgumentException(string.Format("The value of {0} is not a legal bool ({1})", argName, stringValue));
                }
                return result;
            }
            if (parameterType == typeof(string))
            {
                return stringValue;
            }
            throw new ApplicationException();
        }
    }
}